//
//  ViewController.m
//  KVO
//
//  Created by yao zhaofeng on 2018/4/4.
//  Copyright © 2018年 YaoZhaofeng. All rights reserved.
//
/*观察KVO实现原理
 *
 *在XYObject+KVO中模仿实现了KVO的实现
 *
 *如果自己实现了NSKVONotifying_XYObject这个类，那么XYObject对象的KVO就失效了
 *
 *有可能出现的问题：
 *1. 如果观察者没有实现observeValueForKeyPath:ofObject:change:context:这个方法，那么属性变化的时候，就会crash
 *2. 如果同一个对象对另一个对象的同一个属性注册了多次观察，那么属性变化的时候也会通知多次。 此时，remove的时候，也需要remove对应的次数才能remove彻底。(NSNotificationCenter也是如此）
 *
 */

#import "ViewController.h"
#import <objc/runtime.h>
//#import <objc/objc.h>
#import <objc/message.h>
#import "XYObject.h"
#import "XYObject+KVO.h"
#import "Test.h"

@interface ViewController ()
@property (nonatomic, weak) XYObject *myObject;
@end

@implementation ViewController

static NSArray *getClassMethodNames(Class c)
{
    NSMutableArray *resultArr = [NSMutableArray array];
    
    unsigned int methodCount = 0;
    Method *methodList = class_copyMethodList(c, &methodCount);
    for (int i = 0; i < methodCount; i++) {
        [resultArr addObject:NSStringFromSelector(method_getName(methodList[i]))];
    }
    free(methodList);
    
    return resultArr;
}

//object_getClass和obj->isa是同一个东西？
//obj->isa报错，怎么访问isa指针？
static void LogDescription(NSString *name, id obj) {
    NSString *str = [NSString stringWithFormat:
                     @"%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>",
                     name,
                     obj,
                     class_getName([obj class]),
                     object_getClassName(obj),
                     [getClassMethodNames(object_getClass(obj)) componentsJoinedByString:@", "]];
    NSLog(@"%@\n", str);
}

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)test:(NSNotification *)notify {
    NSLog(@"接受到通知了~~");
}

- (void)implementKVOBySelfTest {
    XYObject *x = [[XYObject alloc] init];
    
    self.myObject = x;
    
    [x XY_addObserver:self forKeyPath:@"age" options:0 context:NULL];
    
     [x setAge:100];
}

//观察KVO实现原理
- (void)observeKVOTest {
    XYObject *x = [[XYObject alloc] init];
    XYObject *y = [[XYObject alloc] init];
    XYObject *xy = [[XYObject alloc] init];
    XYObject *control = [[XYObject alloc] init];
    
    [xy addObserver:self forKeyPath:@"x" options:0 context:NULL];
    [y addObserver:self forKeyPath:@"y" options:0 context:NULL];
    [xy addObserver:self forKeyPath:@"y" options:0 context:NULL];
    
    //-class都一样
    //object_getClassName显示，被观察的对象所属类变成了NSKVONotifying_XYKVOObject，他就是动态生成的XYKVOObject的子类
    //对于被观察不同属性的不同实例，只生成了一个子类，将不同实例中被观察的不同属性的set方法都重写了，没有观察的（setZ）没有被重写，
    //从方法列表还可以看出，还重写了-class, dealloc, _isKVOA
    LogDescription(@"control", control);
    LogDescription(@"x", x);
    LogDescription(@"y", y);
    LogDescription(@"xy", xy);
    
    //验证NSKVONotifying_XYKVOObject的父类为XYKVOObject
    NSLog(@"NSKVONotifying_XYKVOObject 父类：%s", class_getName(class_getSuperclass(object_getClass(x))));
    
    //NSMethodSignature指针和IMP是同一种东西？
    //被kvo观察后，setX方法指针发生了变化
    NSLog(@"Using NSObject methods, normal setX: is %p, overridden setX: is %p\n",
          [control methodForSelector:@selector(setX:)],
          [x methodForSelector:@selector(setX:)]);
    NSLog(@"Using libobjc functions, normal setX: is %p, overridden setX: is %p\n",
          method_getImplementation(class_getInstanceMethod(object_getClass(control),
                                                           @selector(setX:))),
          method_getImplementation(class_getInstanceMethod(object_getClass(x),
                                                           @selector(setX:))));
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"keyPath is %@; chage's new value is %@,  old value is %@ ", keyPath, change[NSKeyValueChangeNewKey], change[NSKeyValueChangeOldKey]);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//    [_myObject setX:200];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"123" object:nil];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end
